﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LGPeWJNM.BaseClasses;


namespace LGPeWJNM.Ime
{
    /// <summary>
    /// Método de designação por inserção mais econômica
    /// </summary>
    public class Ime : IRoteirizacao
    {
        /// <summary>
        /// Ordena os agrupamentos de clientes a fim de minimizar os trajetos entre eles em seus grupos
        /// </summary>
        /// <remarks>Implementado através do critério de <i>inserção mais econômica</i></remarks>
        /// <param name="destinos">Conjunto de grupos desordenados de coordenadas de destinos</param>
        /// <param name="deposito">Coordenadas do depósito</param>
        /// <param name="quantidadeVeiculos">Número de veículos disponíveis para a roteirização</param>
        /// <param name="capacidadeVeiculo">Capacidade de carga de cada veículo</param>
        /// <returns>Conjunto de grupos ORDENADOS de coordenadas de destinos</returns>
        public IEnumerable<LinkedList<ICliente>> Processar(IEnumerable<IEnumerable<ICliente>> destinos, Point deposito, int quantidadeVeiculos, float capacidadeVeiculo)
        {
            LinkedList<ICliente>[] resultados = new LinkedList<ICliente>[destinos.Count()];
            System.Threading.Tasks.Parallel.ForEach(destinos, cluster =>
            {
                var restantes = cluster.ToList();
                restantes.Add(new Cliente() { Coordenada = deposito, Demanda = 0, Id = 1 });

                var q1 = from c1 in restantes.Cast<Cliente>()
                         from c2 in restantes.Cast<Cliente>()
                         where c1 != c2
                         group c1.Coordenada % c2.Coordenada by new { c1 = c1.Id < c2.Id ? c1 : c2, c2 = c1.Id > c2.Id ? c1 : c2 } into g1
                         select new { g1.Key.c1, g1.Key.c2, dist = g1.First() } into g1
                         orderby g1.dist
                         select g1;

                var g = q1.First();
                LinkedList<ICliente> subgrafo = new LinkedList<ICliente>(new ICliente[] { g.c1, g.c2, g.c1 });

                restantes.RemoveAll(c => subgrafo.Contains(c));
                while (restantes.Count > 0)
                {
                    var k = restantes.First();
                    restantes.Remove(k);

                    var q2 = from s in subgrafo
                             where subgrafo.Last.Value != s
                             select new { fator = s.Coordenada % k.Coordenada + k.Coordenada % subgrafo.Find(s).Next.Value.Coordenada - s.Coordenada % subgrafo.Find(s).Next.Value.Coordenada, s } into s
                             orderby s.fator
                             select s;
                    subgrafo.AddAfter(subgrafo.Find(q2.First().s), k);
                }
                trazDepositoProInicioDaLista(subgrafo);
                resultados[destinos.ToList().IndexOf(cluster)] = subgrafo;
            });

            return resultados;
        }

        private void trazDepositoProInicioDaLista(LinkedList<ICliente> demaisTrechos)
        {
            if (demaisTrechos.First.Value == demaisTrechos.Last.Value)
                demaisTrechos.RemoveLast();
            while (demaisTrechos.First.Value.Demanda != 0)
            {
                var last = demaisTrechos.Last;
                demaisTrechos.RemoveLast();
                demaisTrechos.AddFirst(last);
            }
            demaisTrechos.AddLast(demaisTrechos.First.Value);
        }
    }
}
